//----BEGIN LICENSE----//
/*****************************************************************\
** prevalentLite - sqlite3 persistence object library 
**
** Copyright (C) 2008 Philip R. Schuchardt
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
*****************************************************************/
//----END LICENSE----//
#ifndef plLIST_H
#define plLIST_H

/*!
  \class plList plList.h <plList>
  \brief Database aware list

  This is a database aware list.  The list can store different any object
  that inherites plData.  The data from the database is cached in std::vector.
  A small subset of std::vector methods are supported

  */

/*---------------------------------------------------------------------
        plList<T> - Database aware list.  Any changes
	in the list effect the database.  The whole list is queried from
	the database.

	Created by: Philip Schuchardt - 2008-11-06
--------------------------------------------------------------------------*/

//Our includes
#include <plData>
#include <plDatabase>

//STD Vector
#include <vector>
#include <string>
#include <sstream>

//Sqlite includes
#include <sqlite3.h>

template <class T>
class plList : public plData {

public:
	//Constructor
        plList();

	//Descructor
        virtual ~plList();

        /*!
            \brief Gets data from the index
            \param index - The index of the element
            \return T
        */
	T at(int index);

        /*!
            \brief Replaces a value at an index
            \param index - The index were the value will be replaced
            \param value - The replacement value
         */
	void replace(int index, const T& value);

        /*!
            \brief Adds the value to the end of the list
            \param value - The value to be added to the list
        */
	void push_back(const T& value);

        /*!
            \brief Gets the number of values in the list
            \return The number of entries
        */
	int size();

        /*!
            \brief Clears all the elements in the array
        */
	void clear();

        /*!
            \brief Loads from database
            Forces the list to load from the database.
            This method can be expensive, because of IO.
          */
	bool LoadFromDB();

        /*!
            \brief Completely Delete the list from the database
        */
        void Delete();
	
	//Binds the list id with the statement
	virtual void Bind(sqlite3_stmt* statement, int index = 1) const;
	
	//Gets the list id from the current statement
	virtual void Column(sqlite3_stmt* statement, int index = 0);
	
	//The classes name
        static const char* GetClassName() { return "plList"; }
	
protected:
	bool Cached;
        std::vector<T>* List;
	long long ListId;
        std::string TableName;

	//Updates the value in the database
	void UpdateDB(int index, const T& value);

	//Inserts the value into the databse
	void InsertDB(int index, const T& value);

	//Clears the database of it's values
	void ClearDB();

	//Creates a list entry in the correct table
	void CreateListDB();

	//Helper to create list DB.  Tries to assign the table a id
	//Return false if can't get an id.  Probably because the table doesn't exist
	bool TryAssignList();

};

//Constructor
template <class T>
plList<T>::plList() {
        List = new std::vector<T>();
	Cached = false;
	ListId = 0;
	
	//The table's name
	TableName = T::GetClassName();
	TableName.append("List");
	
} //end plList()

//Descructor
template <class T>
plList<T>::~plList() {
	delete List;
}

//Gets data from the index
template <class T>
T plList<T>::at(int index) {
	if (!Cached) {
		Cached = LoadFromDB();
	}
	
	T tmp = List->at(index);
	
	return tmp;
} // end at

//Replaces a value at an index
template <class T>
void plList<T>::replace(int index, const T& value) {
	
	if (!Cached) {
		Cached = LoadFromDB();
	}
	
	//We need to make a copy it doesn't complain
	T copyValue;
	copyValue.SetDisabled(true); //Make it retarded
	copyValue = value;

        (*List)[index] = copyValue;

	//Update in the database
	UpdateDB(index, value);

} // end replace

//Gets the count of the list
template <class T>
void plList<T>::push_back(const T& value) {
	if (!Cached) {
		Cached = LoadFromDB();
	}
	
	int index = List->size();
	
	//We need to make a copy and disable it so it doesn't complain
	T copyValue;
	copyValue.SetDisabled(true); //Make it retarded
	copyValue = value;
	
	List->push_back(copyValue);
	
	//Instert into the databse
	InsertDB(index, value);
} //end push_back

//Gets the number of value in the array
template <class T>
int plList<T>::size() {
	if (!Cached) {
		Cached = LoadFromDB();
	}
	return List->size();
} //end count

//Clears all the elements in the array
template <class T>
void plList<T>::clear() {
	List->clear();
	
	//Clear all entries in the database for this index
	ClearDB();
} //end clear

//Loads from the database - Returns if it's cached or not
template <class T>
bool plList<T>::LoadFromDB() {
	
	List->clear();
	
	//Need to get the database and the id of this list
        plDatabase* database = GetDatabase();
	database->GetData(this);
	
	//If we exist as a list
	if(ListId <= 0) {
		CreateListDB();
		return true;
	}
	
	sqlite3* sqliteDB = database->GetSqliteDB();
	
        //Get all entries for the list
        std::stringstream sqlStream;
        sqlStream << "SELECT value FROM " << TableName << " WHERE ListId=" << ListId;
	
	sqlite3_stmt* selectStmt;
        int error = sqlite3_prepare_v2(sqliteDB, sqlStream.str().c_str(), -1, &selectStmt, NULL);
	
	GetDatabase()->ReadOnly(true);
	//If the prepare statement was okay
	if(error == SQLITE_OK) {
		sqlite3_step(selectStmt); //Skep the dump row
		while(sqlite3_step(selectStmt) == SQLITE_ROW) {
			T value;
			value.SetDisabled(true);
			value.Column(selectStmt, 0);
			List->push_back(value);
		}
	} //end if(error == SQLITE_OK
	GetDatabase()->ReadOnly(false);
	
	return true;
	
} //end LoadFromDB

//Completely Delete the list from the database
template <class T>
void plList<T>::Delete() {

    plDatabase* database = GetDatabase();
    sqlite3* sqliteDB = database->GetSqliteDB();

    //Create the sql statement
    std::stringstream sqlStream;
    sqlStream << "DELETE FROM " << TableName << " WHERE ListId=" << ListId;

    int error = sqlite3_exec(sqliteDB, sqlStream.str().c_str(), NULL, NULL, NULL);
    if(error != SQLITE_OK) {
        std::cout << "Error plList::DeleteDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << std::endl;
    }

    //Update data in the parent's object
    ListId = -1;
    SetData(this);

    //Clear the list in memory
    List->Clear();

    //Make sure we un-cache things
    Cached = false;
}

//Binds the list id with the statement
template <class T>
void plList<T>::Bind(sqlite3_stmt* statement, int index) const {
	sqlite3_bind_int64(statement, index, ListId);
}
	
//Gets the list id from the current statement
template <class T>
void plList<T>::Column(sqlite3_stmt* statement, int index ) {
	ListId = sqlite3_column_int64(statement, index);
}

//Updates the value in the database
template <class T>
void plList<T>::UpdateDB(int index, const T& value) {
        plDatabase* database = GetDatabase();
	sqlite3* sqliteDB = database->GetSqliteDB();

        //Create the sql statement
        std::stringstream sqlStream;
        sqlStream << "UPDATE  " << TableName << " SET Value=? WHERE ListId=? AND Idx=?";
	
	sqlite3_stmt* statement;
        int error = sqlite3_prepare_v2(sqliteDB, sqlStream.str().c_str(), -1, &statement, NULL);
	if(error == SQLITE_OK) {
		//Bind all the statements
		sqlite3_bind_int64(statement, 1, ListId);
		sqlite3_bind_int(statement, 2, index);
		value.Bind(statement, 3);
		sqlite3_step(statement);
	}else {
                std::cout << "Error plList::UpdateDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << " " << sqlStream.str() << std::endl;
	}
} //end UpdateDB

//Inserts the value into the databse
template <class T>
void plList<T>::InsertDB(int index, const T& value) {
        plDatabase* database = GetDatabase();
	sqlite3* sqliteDB = database->GetSqliteDB();

        //Create the sql statement
        std::stringstream sqlStream;
        sqlStream << "INSERT INTO " << TableName << " (ListId, Idx, Value) values (?, ?, ?)";
	
	sqlite3_stmt* statement;
        int error = sqlite3_prepare_v2(sqliteDB, sqlStream.str().c_str(), -1, &statement, NULL);
	if(error == SQLITE_OK) {
		//Bind all the statements
		sqlite3_bind_int64(statement, 1, ListId);
		sqlite3_bind_int(statement, 2, index);
		value.Bind(statement, 3);
		sqlite3_step(statement);
	}else {
                std::cout << "Error plList::InsertDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << std::endl;
                std::cout << "Error:" << sqlStream.str().c_str() << std::endl;
	}
} //End InsertDB

//Clears the database of it's values
template <class T>
void plList<T>::ClearDB() {
        plDatabase* database = GetDatabase();
	sqlite3* sqliteDB = database->GetSqliteDB();

        //Create the sql statement
        std::stringstream sqlStream;
        sqlStream << "DELETE FROM " << TableName << " WHERE ListId=" << ListId << "AND Index >= 0";
	
        int error = sqlite3_exec(sqliteDB, sqlStream.str().c_str(), NULL, NULL, NULL);
	if(error != SQLITE_OK) {
                std::cout << "Error plList::ClearDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << std::endl;
	}
} //End ClearDB


//Creates a list entry in the correct table
template <class T>
void plList<T>::CreateListDB() {
	
	//We can insert the new list into the table
	if(!TryAssignList()) {
		//Try to create a new table and indexs
	
                plDatabase* database = GetDatabase();
		sqlite3* sqliteDB = database->GetSqliteDB();
		
                //Create the sql statement
                std::stringstream sqlStream;
                sqlStream << "CREATE TABLE " << TableName << " (ListId INTEGER, Idx INTEGER, Value)";

                int error = sqlite3_exec(sqliteDB, sqlStream.str().c_str(), NULL, NULL, NULL);
		
		//If creating the table went okay
		if(error == SQLITE_OK) {
			//Create indexes
			
			/* PREFORMANCE ENHANSEMENT
			QString indexSQL;
			QTextStream(&indexSQL) << "CREATE UNIQUE INDEX ListIdIndex ON " << TableName << "(IndexId ASC)";
			sqlite3_exec(sqliteDB, (const char*)indexSQL.toAscii(), NULL, NULL, NULL);
			
			indexSQL.clear();
			QTextStream(&indexSQL) << "CREATE INDEX IdxIndex ON " << TableName << "(idx ASC)";
			sqlite3_exec(sqliteDB, (const char*)indexSQL.toAscii(), NULL, NULL, NULL);
			*/
			
		} else {
			//Some sorta weird error can't create tableName
                        std::cout << "Error: Can't create Table: " << TableName << "Sqlite3 error:" << sqlite3_errmsg(sqliteDB) << std::endl;
			return;
		} //end if(error = SQLITE_OK)
		
		//Okay, table should now be created need to try to assign the list again
		if(!TryAssignList()) {
			//Damn another error what is it this time
			std::cout << "Sqlite3 error: " << sqlite3_errmsg(sqliteDB) << std::endl;
		}
		
	} //end if(!TryAssignList)

} //End CreateListDB

//Helper to create list DB.  Tries to assign the table a id
//Return false if can't get an id.  Probably because the table doesn't exist
template <class T>
bool plList<T>::TryAssignList() {
	//Need to get the database and the id of this list
        plDatabase* database = GetDatabase();
	database->GetData(this);
	
	sqlite3* sqliteDB = database->GetSqliteDB();
	
        //Need to get the max id
        std::stringstream maxSQL;
        maxSQL << "SELECT max(ListId) from " << TableName;

	sqlite3_stmt* maxStatement;
        int error = sqlite3_prepare_v2(sqliteDB, maxSQL.str().c_str(), -1, &maxStatement, NULL);
	if(error == SQLITE_OK) {
		//Found the max id for 
		sqlite3_step(maxStatement);
		ListId = sqlite3_column_int64(maxStatement, 0);
		ListId++; //Increas it by one, make it unique
		sqlite3_finalize(maxStatement);
		
		//Set the list id for the owner object 
		database->SetData(this);
	} else {
		//Error probably table not created
		sqlite3_finalize(maxStatement);
		return false;
	}
	
        //First try to insert a dumy node
        std::stringstream insertSQL;
        insertSQL << "INSERT INTO " << TableName << " (ListId, Idx) VALUES (" << ListId << ",-1)";
	
	sqlite3_stmt* statement;
        error = sqlite3_prepare_v2(sqliteDB, insertSQL.str().c_str(), -1, &statement, NULL);
	if(error == SQLITE_OK) {
		//Adds entry to 
		sqlite3_step(statement);
		sqlite3_finalize(statement);
	} else {
		sqlite3_finalize(statement);
                std::cout << "Error plList: Can't insert sql:" << insertSQL << std::endl;
		return false;
	}
	
	return true;
	
} //end TryAssignList

#endif //plLIST_H

